import React, { useCallback, useEffect, useMemo } from 'react';
import { motion } from 'framer-motion';
import { useClickOutside, useLocalStore } from 'hooks';
import { observer } from 'mobx-react-lite';
import MultiDropdownStore from 'store/components/MultiDropdown';

import { ArrowDownIcon } from 'components/icons';
import Input from 'components/Input';
import Text, { TEXT_TAG, TEXT_VIEW } from 'components/Text';
import { classnames } from 'utils';

import classes from './MultiDropdown.module.scss';

export type Option = {
    /** Ключ варианта, используется для отправки на бек/использования в коде */
    key: string;
    /** Значение варианта, отображается пользователю */
    value: string;
};

/** Пропсы, которые принимает компонент Dropdown */
export type MultiDropdownProps = {
    className?: string;
    inputContainerClassName?: string;
    optionsClassName?: string;
    optionClassName?: string;
    singleValue?: boolean;
    /** Массив возможных вариантов для выбора */
    options: Option[];
    /** Текущие выбранные значения поля, может быть пустым */
    value: Option[];
    /** Callback, вызываемый при выборе варианта */
    onChange: (value: Option[]) => void;
    /** Заблокирован ли дропдаун */
    disabled?: boolean;
    /** Возвращает строку которая будет выводится в инпуте. В случае если опции не выбраны, строка должна отображаться как placeholder. */
    getTitle: (value: Option[]) => string;
};

const cnMultiDropdown = classnames(classes, 'multidropdown');

const MultiDropdown: React.FC<MultiDropdownProps> = ({
    className,
    inputContainerClassName,
    optionsClassName,
    optionClassName,
    singleValue,
    options,
    value,
    onChange,
    disabled,
    getTitle,
}) => {
    const { setInputValue, inputValue, setPopoverVisible, popoverVisible, setDropdownRef, dropdownRef, optionsFilter } =
        useLocalStore(() => new MultiDropdownStore());

    const title = useMemo(() => getTitle(value), [getTitle, value]);

    const filteredOptions = useMemo(
        () => options.filter((option) => optionsFilter.test(option.value)),
        [options, optionsFilter],
    );

    const handleOnChange = useCallback((value: string) => {
        setInputValue(value);
    }, []);

    const handleOnClickInput = useCallback(() => {
        if (!disabled) {
            setPopoverVisible(true);
        }
    }, [disabled]);

    const handleOnClickOutside = useCallback(() => {
        setPopoverVisible(false);
        setInputValue('');
    }, []);

    useClickOutside(dropdownRef, handleOnClickOutside);

    const handleOnClickOption = useCallback(
        (newOption: Option) => () => {
            if (singleValue) {
                onChange([newOption]);
            } else if (value.some((option) => option.key === newOption.key)) {
                onChange(value.filter((option) => option.key !== newOption.key));
            } else {
                onChange([...value, newOption]);
            }
        },
        [singleValue, value],
    );

    useEffect(() => {
        if (disabled) {
            setPopoverVisible(false);
        }
    }, [disabled]);

    return (
        <div className={cnMultiDropdown() + ` ${className}`} ref={setDropdownRef}>
            <Input
                value={popoverVisible ? inputValue : value.length ? title : ''}
                onChange={handleOnChange}
                afterSlot={
                    <ArrowDownIcon
                        onClick={handleOnClickInput}
                        color="secondary"
                        cursor={disabled ? 'default' : 'pointer'}
                    />
                }
                disabled={disabled}
                onClick={handleOnClickInput}
                placeholder={title}
                className={inputContainerClassName}
            />
            {popoverVisible && !disabled && (
                <motion.div
                    initial={{ height: 0 }}
                    animate={{ height: 'inherit' }}
                    className={cnMultiDropdown('options') + ` ${optionsClassName}`}
                >
                    {filteredOptions.map((option) => (
                        <motion.span
                            initial={{ opacity: 0 }}
                            whileInView={{ opacity: 1 }}
                            transition={{ delay: 0.2 }}
                            key={option.key}
                            onClick={handleOnClickOption(option)}
                        >
                            <Text
                                tag={TEXT_TAG.div}
                                view={TEXT_VIEW.p16}
                                className={cnMultiDropdown('option') + ` ${optionClassName}`}
                                color={title.includes(option.value) ? 'accent' : 'primary'}
                            >
                                {option.value}
                            </Text>
                        </motion.span>
                    ))}
                </motion.div>
            )}
        </div>
    );
};

export default observer(MultiDropdown);
